#include "core.h"

//Ctrl+C强制终止程序
static void key_control_c(int signal);
//客户端断开连接
static void client_remove(int clnt_sock);
//判断是否已经认证
static bool has_auth(int clnt_sock);
//进行认证
static bool client_auth(int clnt_sock, char *str);
//响应客户端连接在线状态
static void server_online_response(int clnt_sock);
//用户命令请求
static void client_cmd_ls(int clnt_sock);
static void client_cmd_pwd(int clnt_sock);
static void client_cmd_cd(int clnt_sock, char *str);
static void client_cmd_get(int clnt_sock, char *str);
static void client_cmd_put(int clnt_sock, char *str);
static void client_cmd_help(int clnt_sock);
//请求路径的相关函数
static void set_abs_request_path(auth_ptr auth_p, char *path, char *abspath);
static void set_rel_file_path(char *abspath, char *relpath);
static void get_abs_file_path(auth_ptr auth_p, char *path, char *abspath);
static void set_rel_work_path(char *abspath, auth_ptr auth_p);
static void set_abs_work_path(auth_ptr auth_p, char *abspath);
//打开控制连接
static void file_download_ctrl(int clnt_sock, char *paras);
static void file_upload_ctrl(int clnt_sock, char *paras);

void set_locked(pthread_mutex_t *mutex){
	pthread_mutex_init(mutex, NULL);
	pthread_mutex_lock(mutex);
}

void set_unlocked(pthread_mutex_t *mutex){
	pthread_mutex_unlock(mutex);
	pthread_mutex_destroy(mutex);
}

bool load_config(char *fname){
	char buf[CONF_LINE_LEN];
	char *key,*value;
	FILE *fp=fopen(fname, "r");
	int fields[CONF_REQUEST_NUM]={0},i=0,j;

	if(fp == NULL){
		if(errno == EACCES){
			fprintf(stderr, "Unable to read config file %s: Permission denied.\n", fname);
		}
		return false;
	}

	while(fgets(buf, CONF_LINE_LEN, fp) != NULL){
		buf[strlen(buf)-1] = 0;
		if(buf[0] == '#' || !isalpha(buf[0])){
			bzero(buf, CONF_LINE_LEN);
			continue;
		}
		//截取第一个项
		key=strtok(buf, CONF_ITEM_DELIM);
		if(key == NULL){
			bzero(buf, CONF_LINE_LEN);
			continue;
		}
		if((value=strtok(NULL, "=")) == NULL){
			bzero(buf, CONF_LINE_LEN);
			continue;
		}
		if(!strcmp(ROOT_PATH, key)){//ftp根目录
			int len=strlen(value);//去最后的/ 47

			if(len >= 1 && value[len-1] == 47){
				value[len-1] = 0;
			}
			strcpy(conf_ptr->root, value);
			fields[i++] = 1;
		}else if(!strcmp(USER_FILE, key)){
			strcpy(conf_ptr->user, value);
			fields[i++] = 1;
		}else if(!strcmp(CTRL_PORT, key)){
			conf_ptr->port = atoi(value);
			fields[i++] = 1;
		}else if(!strcmp(LOG_PATH, key)){
			bzero(&conf_ptr->logpath, LOG_PATH_LEN);
			int len=strlen(value);//加上的/ 47

			if(len >= 1 && value[len-1] != 47){
				value[len] = 47;
			}
			strcpy(conf_ptr->logpath, value);
			fields[i++] = 1;
		}else if(!strcmp(MAX_CLIENT, key)){
			conf_ptr->client = atoi(value);
			fields[i++] = 1;
		}else if(!strcmp(MAX_THREAD, key)){
			conf_ptr->thread = atoi(value);
			fields[i++] = 1;
		}else if(!strcmp(TIME_OUT, key)){
			conf_ptr->timeout = atoi(value);
			fields[i++] = 1;
		}else if(!strcmp(PID_FILE, key)){
			strcpy(conf_ptr->pidfile, value);
			fields[i++] = 1;
		}else{//其他项，忽略
			continue;
		}
		bzero(buf, CONF_LINE_LEN);
	}
	fclose(fp);

	for(j=0; j<CONF_REQUEST_NUM; j++){
		if(fields[j] == 0){
			switch(j){
				case 0:
					puts("root_path error.");
					break;
				case 1:
					puts("user_file error.");
					break;
				case 2:
					puts("listen_port error.");
					break;
				case 3:
					puts("log_path error.");
					break;
				case 4:
					puts("max_client error.");
					break;
				case 5:
					puts("max_thread error.");
					break;
				case 6:
					puts("time_out error.");
					break;
				case 7:
					puts("pid_file error.");
					break;
				default:
					puts("unknown error.");
			}
			return false;
		}
	}
	//检测用户文件可读性，日志文件可写性
	if(!is_readable(conf_ptr->user)){
		puts("users file is not readable.");
		return false;
	}
	if(!is_dir(conf_ptr->logpath) || !is_writable(conf_ptr->logpath)){
		puts("log path is not writable.");
		return false;
	}

	if(DEBUG){
		printf(" Configure: \n%10s: %s\n%10s: %s\n%10s: %s\n%10s: %d\n%10s: %d\n%10s: %d\n%10s: %d\n%10s: %s\n",
				ROOT_PATH, conf_ptr->root,
				USER_FILE, conf_ptr->user,
				LOG_PATH, conf_ptr->logpath,
				CTRL_PORT, conf_ptr->port,
				MAX_CLIENT, conf_ptr->client,
				MAX_THREAD, conf_ptr->thread,
				TIME_OUT, conf_ptr->timeout,
				PID_FILE, conf_ptr->pidfile);
	}
	//设定服务器根目录长度
	rlen = strlen(conf_ptr->root);
	//写入日志
	set_log_to_file("Start service successful.");

	return true;
}

bool load_user(char *fname){
	FILE *fp=fopen(fname, "r");
	if(fp == NULL){
		return false;
	}
	user_init(&user_list);
	//打开配置文件
	if(!user_parser(user_list, fp)){
		fclose(fp);
		user_destroy(&user_list);
		return false;
	}
	//user_traverse(user_list, print_u);

	fclose(fp);
	return true;
}

//设置为非阻塞模式
bool set_nonblock_mode(int clnt_sock){
	int flags = fcntl(clnt_sock, F_GETFL, 0);
	flags |= O_NONBLOCK;
	if(fcntl(clnt_sock, F_SETFL, flags) == -1)
		return false;
	return true;
}

/**
 * 服务器端被终止
 * 关闭所有套接字
 */
static void key_control_c(int signal){
	if(signal == SIGINT){
		puts("\nCtrl+C Pressed. Ask All Clients To Terminate...");

		server_resource_revoke();
		exit(4);
	}
}

void server_resource_revoke(){
	if(auth_list != NULL){
		auth_destroy(&auth_list);
	}
	if(user_list != NULL){
		user_destroy(&user_list);
	}
	if(logfp != NULL){
		fclose(logfp);
	}
	if(ep_events != NULL){
		free(ep_events);
		ep_events = NULL;
	}
	if(epfd != -1){
		close(epfd);
		epfd = -1;
	}
	if(serv_sock != -1){
		close(serv_sock);
		serv_sock = -1;
	}
	if(data_sock != -1){
		close(data_sock);
		data_sock = -1;
	}
}

void quick_restart(int serv_sock){
	socklen_t optlen;
	int option;

	optlen = sizeof(option);
	option = true;
	setsockopt(serv_sock, SOL_SOCKET, SO_REUSEADDR, (void *)&option, optlen);
}

void epoll_init(int cnt_max){
	epfd=epoll_create(cnt_max);
	ep_events=(struct epoll_event *)malloc(sizeof(struct epoll_event) * cnt_max);
}

void key_control_c_register(){
	struct sigaction key_ctrl_c_act;
	key_ctrl_c_act.sa_handler = key_control_c;
	sigemptyset(&key_ctrl_c_act.sa_mask);
	key_ctrl_c_act.sa_flags = 0;
	sigaction(SIGINT, &key_ctrl_c_act, 0);
}

void server_handler(int serv_sock){
	int clnt_sock;
	struct sockaddr_in clnt_addr;
	socklen_t clnt_addr_sz;
	struct epoll_event event;
	auth auth_n;
	auth_ptr auth_p=NULL;

	clnt_addr_sz=sizeof(clnt_addr);
	clnt_sock=accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_sz);
	//边缘触发非阻塞方式
	set_nonblock_mode(clnt_sock);
	event.events = EPOLLIN | EPOLLET;
	event.data.fd = clnt_sock;
	if((auth_p=auth_find(auth_list, clnt_sock)) != NULL){
		auth_remove(auth_list, clnt_sock);
	}
	//写入待认证列表
	auth_n.auth = 0;
	auth_n.sock = clnt_sock;
	strcpy(auth_n.ipv4, inet_ntoa(clnt_addr.sin_addr));
	auth_n.port = ntohs(clnt_addr.sin_port);
	strcpy(auth_n.curpath, "/");
	auth_push(auth_list, &auth_n);
	//加入监视
	epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
}

void *client_handler(void *para){
	int recv_len,cmd,state;
	char buf[STR_BUF_SZ], logmsg[LOG_MSG_LEN];
	int clnt_sock=*((int *)para);
	auth_ptr auth_p;

	auth_p=auth_find(auth_list, clnt_sock);
	//处理请求
	while(true){
		recv_len=read(clnt_sock, buf, STR_BUF_SZ);
		if(recv_len <= 0){
			continue;
		}
		//解析命令字及状态
		sscanf(buf, "%d %d", &cmd, &state);
		if(DEBUG){
			printf("Command: %d\tState: %d\n", cmd, state);
		}
		//判断是否为查询状态指令
		if(cmd == CMD_CONN && state == STAT_ALIVE){
			server_online_response(clnt_sock);
			continue;
		}
		bzero(logmsg, LOG_MSG_LEN);
		sprintf(logmsg, "%s:%d ", auth_p->ipv4, auth_p->port);
		//判断是否为注销指令
		if(cmd == CMD_BYE && (state == STAT_CREADY || state == STAT_CSTOP)){
			strcat(strcat(logmsg, auth_p->uname), " client quit");
			set_log_to_file(logmsg);
			client_remove(clnt_sock);
			break;
		}
		//进行认证
		if(cmd == CMD_AUTH && state == STAT_CREADY){
			if(!client_auth(clnt_sock, buf)){
				if(DEBUG){
					puts("client_auth() failed.");
				}
				client_remove(clnt_sock);
				break;
			}
			strcat(strcat(logmsg, auth_p->uname), " client auth successful.");
			if(DEBUG){
				printf("UserName: %s\tPriviledges: %s \n", auth_p->uname,
						auth_p->priv);
			}
			//返回状态码和用户能使用的命令
			memset(&buf, 0, sizeof(buf));
			sprintf(buf, "%-4d%s", STAT_SREADY, auth_p->priv);
			write(clnt_sock, buf, recv_len);
		}else{
			if(!has_auth(clnt_sock)){
				if(DEBUG){
					puts("has_auth() failed.");
				}
				client_remove(clnt_sock);
				break;
			}
		}
		if(cmd == CMD_LS && state == STAT_CREADY){
			client_cmd_ls(clnt_sock);
			strcat(strcat(strcat(logmsg, auth_p->uname), " "),buf+(CMD_LEN+STATE_LEN)*sizeof(char));
		}else if(cmd == CMD_PWD && state == STAT_CREADY){
			client_cmd_pwd(clnt_sock);
			strcat(strcat(strcat(logmsg, auth_p->uname), " "),buf+(CMD_LEN+STATE_LEN)*sizeof(char));
		}else if(cmd == CMD_CD && state == STAT_CREADY){
			client_cmd_cd(clnt_sock, buf+(CMD_LEN+STATE_LEN)*sizeof(char));
			strcat(strcat(strcat(logmsg, auth_p->uname), " "),buf+(CMD_LEN+STATE_LEN)*sizeof(char));
		}else if(cmd == CMD_GET){
			if(state == STAT_CREADY){
				client_cmd_get(clnt_sock, buf+(CMD_LEN+STATE_LEN)*sizeof(char));
				strcat(strcat(strcat(logmsg, auth_p->uname), " "),buf+(CMD_LEN+STATE_LEN)*sizeof(char));
			}else if(state == STAT_F_W){
				file_download_ctrl(clnt_sock, buf+(CMD_LEN+STATE_LEN)*sizeof(char));
			}
		}else if(cmd == CMD_PUT){
			if(state == STAT_CREADY){
				client_cmd_put(clnt_sock, buf+(CMD_LEN+STATE_LEN)*sizeof(char));
				strcat(strcat(strcat(logmsg, auth_p->uname), " "),buf+(CMD_LEN+STATE_LEN)*sizeof(char));
			}else if(state == STAT_F_OK){
				file_upload_ctrl(clnt_sock, buf);
			}
		}else if(cmd == CMD_HELP && state == STAT_CREADY){
			client_cmd_help(clnt_sock);
			strcat(strcat(strcat(logmsg, auth_p->uname), " "),buf+(CMD_LEN+STATE_LEN)*sizeof(char));
		}else{
			//do nothing
		}
		if(state != STAT_F_OK && state != STAT_F_W){
			set_log_to_file(logmsg);
		}
		if(DEBUG){
			puts("Command Over Here.");
		}
	}

	return 	NULL;
}

void get_cur_log_file(char *logname){
	time_t curtime;
	struct tm *timeinfo;
	char suffix[9];

	bzero(logname, FNAME_LEN);
	strcpy(logname, conf_ptr->logpath);
	strcat(logname, "ftp");
	time(&curtime);
	timeinfo=localtime(&curtime);
	strftime(suffix, sizeof(suffix), "%Y%m%d", timeinfo);
	strcat(strcat(logname, suffix), ".log");
}

void set_cur_log_file(){
	char logname[FNAME_LEN];

	get_cur_log_file(logname);
	if(strcmp(logfile, logname) != 0){
		if(logfp != NULL){
			fclose(logfp);
		}
		bzero(logfile, FNAME_LEN);
		strcpy(logfile, logname);
		logfp=fopen(logfile, "a+");
	}
}

void set_log_to_file(char *info){
	time_t curtime;
	struct tm *timeinfo;
	char format[DATE_FORMAT_LEN];

	time(&curtime);
	timeinfo=localtime(&curtime);
	strftime(format, DATE_FORMAT_LEN, "%c", timeinfo);

	set_cur_log_file();
	fprintf(logfp, "%-*s%s\n", DATE_FORMAT_LEN, format, info);
	fflush(logfp);
}

void shell_exec_stream(char *sh, FILE *fp){
	FILE *pfp;
	char buf[STR_BUF_SZ]={0};
	int len,total=0;
	if(DEBUG){
		printf("command: %s \n", sh);
	}

	if ((pfp = popen(sh, "r")) != NULL) {
		while((len=fread(buf, 1, STR_BUF_SZ, pfp)) != EOF){
			fwrite((void *)buf, STR_BUF_SZ, 1, fp);
			if(DEBUG){
				total += len;
				printf("%s", buf);
			}
			bzero(&buf, STR_BUF_SZ);
			if(ferror(pfp) || feof(pfp) || len < STR_BUF_SZ){
				break;
			}
		}
		if(DEBUG){
			printf("Total send %d bytes. \n", total);
		}
		fflush(fp);
		pclose(pfp);
	}
	fclose(fp);
}

static void client_cmd_cd(int clnt_sock, char *str){
	char cd[2], path[PATH_LEN],abspath[ABS_PATH_LEN],buf[STR_BUF_SZ];
	auth_ptr auth_p=auth_find(auth_list, clnt_sock);
	DIR *dp;

	if(auth_p != NULL){
		sscanf(str, "%s %s", cd, path);
		set_abs_request_path(auth_p, path, abspath);
		if((dp=opendir(abspath)) != NULL){
			set_rel_work_path(abspath, auth_p);
			sprintf(buf, "%-4d%s", STAT_F_OK, auth_p->curpath);
		}else{
			sprintf(buf, "%-4d%s", STAT_F_BAD, "dir not exists or permission denied.");
		}
		write(clnt_sock, buf, STR_BUF_SZ);
	}
}

static void client_cmd_get(int clnt_sock, char *str){
	char temp[3], buf[STR_BUF_SZ], filename[FNAME_LEN], filepath[ABS_PATH_LEN];
	auth_ptr auth_p=auth_find(auth_list, clnt_sock);
	int state;//状态码
	sscanf(str, "%s%s", temp, filename);
	get_abs_file_path(auth_p, filename, filepath);
	bzero(&filename, FNAME_LEN);
	set_rel_file_path(filepath, filename);
	//文件不可读，命令无效
	if(!is_readable(filepath)){
		sprintf(buf, "%-4d%s", STAT_F_NR, "Not readable.");
		write(clnt_sock, buf, STR_BUF_SZ);
		return;
	}

	sprintf(buf, "%-4d%s", STAT_F_R, filename);
	write(clnt_sock, buf, STR_BUF_SZ);
}

static void client_cmd_put(int clnt_sock, char *str){
	char buf[STR_BUF_SZ], abspath[ABS_PATH_LEN];
	auth_ptr auth_p=auth_find(auth_list, clnt_sock);

	if(auth_p != NULL){
		//检查当前目录是否具有可写权限
		set_abs_work_path(auth_p, abspath);
		if(!is_writable(abspath)){
			sprintf(buf, "%-4d%s", STAT_F_NW, "current directory is not writable.");
			write(clnt_sock, buf, STR_BUF_SZ);
			return;
		}
		sprintf(buf, "%-4d%s", STAT_F_W, "current directory is writable.");
		write(clnt_sock, buf, STR_BUF_SZ);
		bzero(&buf, STR_BUF_SZ);
	}
}

static void client_cmd_help(int clnt_sock){
	char buf[STR_BUF_SZ];
	auth_ptr auth_p=auth_find(auth_list, clnt_sock);

	if(auth_p != NULL){
		sprintf(buf, "%-4dcommand avialable: %s", STAT_DONE, auth_p->priv);
		write(clnt_sock, buf, STR_BUF_SZ);
	}
}

static void client_cmd_pwd(int clnt_sock){
	char buf[STR_BUF_SZ];
	auth_ptr auth_p=auth_find(auth_list, clnt_sock);

	bzero(buf, STR_BUF_SZ);
	sprintf(buf, "%s", auth_p->curpath);
	write(clnt_sock, buf, STR_BUF_SZ);
}

static void client_cmd_ls(int clnt_sock){
	char buf[STR_BUF_SZ]={0}, abspath[ABS_PATH_LEN];
	auth_ptr auth_p=auth_find(auth_list, clnt_sock);

	if(auth_p != NULL){
		set_abs_work_path(auth_p, abspath);
		bzero(buf, STR_BUF_SZ);
		strcat(strcat(buf, "ls -l "), abspath);
		shell_exec_stream(buf, fdopen(dup(clnt_sock), "w"));
		bzero(&buf, STR_BUF_SZ);
		sprintf(buf, "%-4d", STAT_DONE);
		if(DEBUG){
			puts(buf);
		}
		write(clnt_sock, buf, STR_BUF_SZ);
	}
}

static void file_upload_ctrl(int clnt_sock, char *buf){
	int start, len;
	char filename[PATH_LEN], filepath[ABS_PATH_LEN];
	auth_ptr auth_p=auth_find(auth_list, clnt_sock);
	FILE *fp;

	if(auth_p != NULL){
		sscanf(buf+(CMD_LEN+STATE_LEN)*sizeof(char), "%10d%3d%255s", &start, &len, filename);
		get_abs_file_path(auth_p, filename, filepath);
		//printf("Start Length: %d, Length: %d, FileName: %s \n", start, len, filename);
		fp=fopen(filepath, "ab+");

		if(fp != NULL){
			fseek(fp, start, SEEK_SET);
			//printf("Current Postion: %ld \n", ftell(fp));
			fwrite(buf+HEADER_LEN*sizeof(char), 1, len, fp);
			fflush(fp);
			fclose(fp);
		}
	}
}

static void file_download_ctrl(int clnt_sock, char *paras){
	int start,len;
	char filename[PATH_LEN], buf[STR_BUF_SZ], filepath[ABS_PATH_LEN],wlen[4];
	auth_ptr auth_p=auth_find(auth_list, clnt_sock);
	FILE *fp;
	char hexlen[3]={0};

	sscanf(paras, "%d%s", &start, filename);
	get_abs_file_path(auth_p, filename, filepath);
	if(DEBUG){
		printf("Request file name: %s \n", filename);
		printf("Request path name: %s \n", filepath);
	}
	fp=fopen(filepath, "rb");
	if(fp == NULL){//文件不可读
		sprintf(buf, "%-4d%s", STAT_F_BAD, "File mode changed or file has been removed.");
		write(clnt_sock, buf, STR_BUF_SZ);
		return;
	}else{
		sprintf(buf, "%-4d%s", STAT_F_OK, "File ok, start to transfer.");
		write(clnt_sock, buf, STR_BUF_SZ);
	}
	bzero(&buf, STR_BUF_SZ);
	int count=0;
	int fd = dup(clnt_sock);
	FILE *wfp=fdopen(fd, "w");
	while((len=fread(buf, 1, STR_BUF_SZ, fp)) >0){
		fprintf(wfp, "%03X", len);
		fwrite(buf, 1, STR_BUF_SZ, wfp);
		bzero(&buf, STR_BUF_SZ);
		if(DEBUG){
			printf("Writes %d Bytes. \n", len);
		}
		count += len;
		if(ferror(fp) || feof(fp) || len < STR_BUF_SZ){
			break;
		}
	}
	if(DEBUG){
		printf("Write %d Bytes In Total.\n", count);
	}
	fflush(wfp);
	fclose(wfp);
	close(fd);
	fclose(fp);
}

static void set_rel_file_path(char *abspath, char *relpath){
	char *p;

	p=strrchr(abspath, '/');
	p++;
	strcpy(relpath, p);
}

static void set_rel_work_path(char *abspath, auth_ptr auth_p){
	char swap[PATH_LEN];

	strcpy(swap, abspath+rlen*sizeof(char));
	bzero(&auth_p->curpath, PATH_LEN);
	strcpy(auth_p->curpath, swap);
}

static void set_abs_work_path(auth_ptr auth_p, char *abspath){
	bzero(abspath, ABS_PATH_LEN);
	strcat(strcat(abspath, conf_ptr->root), auth_p->curpath);
}

static void get_abs_file_path(auth_ptr auth_p, char *path, char *abspath){
	set_abs_request_path(auth_p, path, abspath);
	abspath[strlen(abspath) -1] = 0;
}

//如果路径名以“/”开头，则路径=根路径+请求路径，否则直接在路径=根路径+当前路径+请求路径组成
static void set_abs_request_path(auth_ptr auth_p, char *path, char *abspath){
	int len=strlen(path);

	bzero(abspath, ABS_PATH_LEN);
	strcat(abspath, conf_ptr->root);
	if(path[0] == 47){//以“/”开头的
		strcat(abspath, path);
	}else{
		strcat(strcat(abspath, auth_p->curpath), path);
	}
	if(path[len-1] != 47){
		strcat(abspath, "/");
	}
}

static void server_online_response(int clnt_sock){
	char buf[STR_BUF_SZ];
	sprintf(buf, "%-4d", STAT_ALIVE);
	write(clnt_sock, buf, STR_BUF_SZ);
}

static bool client_auth(int clnt_sock, char *str){
	int cmd,state;
	user_ptr user_p;
	auth_ptr auth_p;
	char uname[UNAME_LEN],passwd[PASSWD_LEN];

	sscanf(str, "%d %d %s %s", &cmd, &state, uname, passwd);
	if(DEBUG){
		printf("Cmd: %d, State: %d, UserName: %s, PassWord: %s\n", cmd, state, uname, passwd);
	}

	if((user_p=user_auth(user_list, uname, passwd)) != NULL){
		auth_p=auth_find(auth_list, clnt_sock);
		if(auth_p != NULL){
			auth_p->auth = 1;
			strcpy(auth_p->priv, user_p->priv);
			strcpy(auth_p->uname, user_p->uname);

			return true;
		}
	}

	return false;
}

static bool has_auth(int clnt_sock){
	auth_ptr auth_p=auth_find(auth_list, clnt_sock);
	if(DEBUG){
		puts("Authenticated.");
	}

	if(auth_p != NULL && auth_p->auth == 1){
		return true;
	}

	return false;
}

static void client_remove(int clnt_sock){
	if(DEBUG){
		auth_ptr auth_p = auth_find(auth_list, clnt_sock);
		printf("client from %s:%d will be removed. \n", auth_p->ipv4, auth_p->port);
	}
	auth_remove(auth_list, clnt_sock);
	epoll_ctl(epfd, EPOLL_CTL_DEL, clnt_sock, NULL);
	close(clnt_sock);
}
